<div id='view' style='width: 100%; height: 100%; background-color: black;'></div>
<script>

Array.prototype.random = function() {
	return this[Math.floor(Math.random() * this.length)];
};

var colorUtil = {
	_rgb : {'r' : 1, 'g' : 3, 'b' : 5},
	_reg : (new RegExp('^#[0-9ABCDEF]{6}$', 'i')),
	_get : function(in_col16, in_rgb) {
		var c16 = '00';
		if (this._reg.test(in_col16)) {
			c16 = in_col16.substr(this._rgb[in_rgb], 2);
		}
		return Number('0x' + c16);
	},
	_set : function(in_col16, in_diffs) {
		var ret = '#';
		for (var rgb in this._rgb) {
			var c = this._get(in_col16, rgb);
			if (in_diffs[rgb]) {
				c += in_diffs[rgb];
			}
			var s = Math.min(255, Math.max(0, c)).toString(16);
			if (c < 0x10) {
				s = '0' + s;
			}
			ret += s;
		}
		return ret;
	},
	hash : function(in_col16, in_var) {
		var diffs = {};
		for (var rgb in this._rgb) {
			diffs[rgb] = Math.round((0.5 - Math.random()) * in_var * 2);
		}
		return this._set(in_col16, diffs);
	},
	bright : function(in_col16, in_diff) {
		var diffs = {};
		for (var rgb in this._rgb) {
			diffs[rgb] = in_diff;
		}
		return this._set(in_col16, diffs);
	}
};

function cPiece(in_baseColor)
{
	this._color = colorUtil.hash(in_baseColor, this._conf.hashValue);
	this._brightStart = -1;
	this._inHighLight = false;
}

cPiece.prototype = {
	_conf : {
		hashValue : 50,
		brightInterval : 200,
	},
	_bind : function(prop) {
		return (function(self) {
			return function() {
				self[prop].apply(self);
			};
		})(this);
	},
	color : function() {
		if (!this._inHighLight) {
			return this._color;
		}
		var ms = (+ new Date()) - this._brightStart;
		if (ms < this._conf.brightInterval) {
			var half = this._conf.brightInterval / 2;
			var bright = Math.min(255, Math.floor((half - Math.abs(ms - half)) / half * 255));
			return colorUtil.bright(this._color, bright);
		} else {
			this._inHighLight = false;
			return this._color;
		}
	},
	highLight : function() {
		if (!this._inHighLight) {
			this._inHighLight = true;
			this._brightStart = (+ new Date());
		}
	}
};

function cBelt(in_w, in_h, in_wCnt, in_pixPer100msec)
{
	this._pieceRect = {
		w : in_w,
		h : in_h
	};
	this._beltLen = in_w * in_wCnt;
	this._timeStamp = -1;
	this._pixPer100msec = in_pixPer100msec;
	this._pieces = [];
	for (var i = 0; i < in_wCnt; i++) {
		this._pieces.push(new cPiece(this._conf.baseColor));
	}
}

cBelt.prototype = {
	_conf : {
		baseColor : '#000066',
		interval : 200,
		highLightRate : 0.1
	},
	_bind : function(prop) {
		return (function(self) {
			return function() {
				self[prop].apply(self);
			};
		})(this);
	},
	_highLight : function() {
		if (Math.random() > (1 - this._conf.highLightRate)) {
			this._pieces.random().highLight();
		}
	},
	draw : function(in_ctx, in_l, in_t) {
		var move = Math.floor(this._pixPer100msec * ((+ new Date()) - this._timeStamp) / 100);
		/*
			<----+----+----+----> : this._beltLen
			<------------>        : len1 (length)
			          <-->        : len2 (length)
			<----+---->           : cnt (count of this._pieceRect.w)
		*/
		var len1 = move % this._beltLen;
		var len2 = len1 % this._pieceRect.w;
		var cnt = (len1 - len2) / this._pieceRect.w;
		for (var i = 0; i < this._pieces.length; i++) {
			in_ctx.fillStyle = this._pieces[i].color();
			var left = in_l;
			if (i < cnt) {
				/* start : (this._beltLen - len1) */
				left += (this._beltLen - len1) + (i * this._pieceRect.w);
				in_ctx.fillRect(left, in_t, this._pieceRect.w, this._pieceRect.h);
			} else if (i == cnt) {
				/* start : 0 & (this._beltLen - len2) */
				in_ctx.fillRect(left, in_t, this._pieceRect.w - len2, this._pieceRect.h);
				left += (this._beltLen - len2);
				in_ctx.fillRect(left, in_t, len2, this._pieceRect.h);
			} else {
				/* start : len2 */
				left += (this._pieceRect.w - len2) + ((i - cnt - 1) * this._pieceRect.w);
				in_ctx.fillRect(left, in_t, this._pieceRect.w, this._pieceRect.h);
			}
		}
	},
	start : function() {
		this._timeStamp = (+ new Date());
		window.setInterval(this._bind('_highLight'), this._conf.interval);
	}
};

var cMirrorBall = {
	_bh : -1,
	_mw : -1,
	_mh : -1,
	_belts : [],
	create : function(in_w, in_h, in_wCnt, in_hCnt) {
		this._bh = in_h;
		this._mw = in_w * in_wCnt;
		this._mh = in_h * in_hCnt;
		for (var i = 0; i < in_hCnt; i++) {
			var pixPer100msec = Math.floor(Math.pow(in_hCnt - Math.abs(in_hCnt / 2 - i), 2) / 50);
			var belt = new cBelt(in_w, in_h, in_wCnt, pixPer100msec);
			belt.start();
			this._belts.push(belt);
		}
	},
	draw : function(in_ctx, in_l, in_t) {
		/* (1) */
		in_ctx.beginPath();
		in_ctx.rect(0, 0, this._mw, this._mh);
		in_ctx.fillStyle = '#000000';
		in_ctx.fill();
		/* (2) */
		in_ctx.beginPath();
		var edge = Math.min(this._mw, this._mh);
		var r1 = edge / 2;
		in_ctx.arc(in_l + r1, in_t + r1, r1, 0, Math.PI * 2, false);
		in_ctx.clip();
		for (var i = 0; i < this._belts.length; i++) {
			this._belts[i].draw(in_ctx, in_l, in_t + (this._bh * i));
		}
		/* (3) */
		in_ctx.beginPath();
		var r2 = edge / 3;
		in_ctx.arc(in_l + r2, in_t + r2, r2, 0, Math.PI * 2, false);
		var gradient = in_ctx.createRadialGradient(in_l + r2, in_t + r2, 0, in_l + r2, in_t + r2, r2);
		gradient.addColorStop(0, 'rgba(255, 255, 255, 0.6)');
		gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
		in_ctx.fillStyle = gradient;
		in_ctx.fill();
	}
};


canvas = document.createElement('CANVAS');
canvas.width  = 500;
canvas.height = 500;
document.getElementById('view').appendChild(canvas);
var ctx = canvas.getContext('2d');
cMirrorBall.create(8, 8, 15, 15);

function render()
{
	requestAnimationFrame(render);
	cMirrorBall.draw(ctx, 10, 10);
}

render();

</script>



